package iec104

import (
	"github.com/wendy512/go-iecp5/asdu"
	"github.com/wendy512/iec104/pkg/db"
	"github.com/wendy512/iec104/pkg/global"
	"github.com/wendy512/iec104/pkg/logger"
	"math"
	"strings"
	"sync"
	"time"
)

const (
	commonAddr = 1
)

type ServerHandler struct {
}

var connHistory = sync.Map{}

func repeatRespond(conn asdu.Connect, cache db.ReadCache) {

	netConnAddr := conn.UnderlyingConn().RemoteAddr().String()
	netConnIpSplit := strings.Split(netConnAddr, ":")
	if len(netConnIpSplit) <= 0 {
		logger.Errorf("ServerHandler", "repeatRespond netConnAddr:%s get ip error", netConnAddr)
	}

	netConnIp := netConnIpSplit[0]

	// 占用连接历史里当前ip的值
	timeValue := time.Now().Format("20060102150405.999999999")
	connHistory.Store(netConnIp, timeValue)

	logger.Infof("ServerHandler", "repeatRespond netConnIp:%s timeValue:%v do send", netConnIp, timeValue)

	ticker := time.Tick(time.Duration(global.Config.ReadFreq) * time.Millisecond)

	for range ticker {
		// 检查连接历史里当前ip的值是否发生变化 控制当前goroutine是否继续
		if hisTimeValue, _ := connHistory.Load(netConnIp); timeValue != hisTimeValue {
			logger.Warnf("ServerHandler", "repeatRespond netConnIp:%s timeValue:%v stoped", netConnIp, timeValue)
			break
		}

		checkResult := checkCacheChangeResult(&cache)
		sendMeasuredValueFloatByCP56Time2a(conn, checkResult)
	}
}

func checkCacheChangeResult(oldCache *db.ReadCache) db.ReadCache {
	newCache := db.LatestCache.GetF32Copy()

	checkResult := db.ReadCache{
		F32:      make(map[string]float32),
		DataTime: newCache.DataTime,
	}

	if len(newCache.F32) != len(oldCache.F32) {
		oldCache.F32 = newCache.F32
		oldCache.DataTime = newCache.DataTime
		checkResult.F32 = newCache.F32
		return checkResult
	}

	for _, readConfig := range global.Config.Read {
		for _, field := range readConfig.Fields {

			cacheField := global.GetFieldKey(readConfig, field)

			newValue, newHave := newCache.F32[cacheField]
			oldValue, oldHave := oldCache.F32[cacheField]

			if !newHave && !oldHave {
				continue
			}

			// 变化量阈值
			threshold := field.Threshold

			if newHave != oldHave {
				if newHave {
					oldCache.F32[cacheField] = newValue
					checkResult.F32[cacheField] = newValue
				}
				if oldHave {
					oldCache.F32[cacheField] = newValue
					checkResult.F32[cacheField] = newValue
				}
				continue
			}

			if math.Abs(float64(oldValue-newValue)) > float64(threshold) {
				oldCache.F32[cacheField] = newValue
				checkResult.F32[cacheField] = newValue
			}
		}
	}

	return checkResult
}

func checkCacheChange(oldCache *db.ReadCache) bool {

	newCache := db.LatestCache.GetF32Copy()

	if len(newCache.F32) != len(oldCache.F32) {
		*oldCache = newCache
		return true
	}

	for key := range newCache.F32 {
		value, oldHave := oldCache.F32[key]
		if !oldHave {
			*oldCache = newCache
			return true
		}

		if value != newCache.F32[key] {
			*oldCache = newCache
			return true
		}
	}

	return false
}

func sendMeasuredValueFloat(conn asdu.Connect, cache db.ReadCache) error {
	measuredValues := make([]asdu.MeasuredValueFloatInfo, len(cache.F32))

	for idxRead, readConfig := range global.Config.Read {
		for idxField, field := range readConfig.Fields {
			cacheField := global.GetFieldKey(readConfig, field)
			if latestFieldVar, ok := cache.F32[cacheField]; ok {
				measuredValues[idxRead*idxField] = asdu.MeasuredValueFloatInfo{
					Ioa:   field.Ioa,
					Value: latestFieldVar,
					Qds:   asdu.QDSGood,
					Time:  cache.DataTime,
				}
			}
		}
	}

	err := asdu.MeasuredValueFloat(conn,
		false,
		asdu.CauseOfTransmission{IsTest: false, IsNegative: false, Cause: asdu.Periodic},
		commonAddr,
		measuredValues...,
	)

	logger.Infof("ServerHandler", "sendMeasuredValueFloat send conn: %v t:%v var:%v", conn.UnderlyingConn().RemoteAddr(), cache.DataTime, measuredValues)

	return err
}

func sendMeasuredValueFloatByCP56Time2a(conn asdu.Connect, cache db.ReadCache) {
	if len(cache.F32) <= 0 {
		logger.Errorf("ServerHandler", "sendMeasuredValueFloatByCP56Time2a: cache is empty")
		return
	}

	var measuredValues []asdu.MeasuredValueFloatInfo

	for _, readConfig := range global.Config.Read {
		for _, field := range readConfig.Fields {
			cacheField := global.GetFieldKey(readConfig, field)
			if latestFieldVar, ok := cache.F32[cacheField]; ok {
				measuredValues = append(measuredValues, asdu.MeasuredValueFloatInfo{
					Ioa:   field.Ioa,
					Value: latestFieldVar,
					Qds:   asdu.QDSGood,
					Time:  cache.DataTime,
				})
			}
		}
	}

	err := asdu.MeasuredValueFloatCP56Time2a(conn,
		asdu.CauseOfTransmission{IsTest: false, IsNegative: false, Cause: asdu.Spontaneous},
		commonAddr,
		measuredValues...,
	)

	if err != nil {
		logger.Errorf("ServerHandler", "sendMeasuredValueFloatByCP56Time2a send error conn: %v t:%v var:%v err:%v", conn.UnderlyingConn().RemoteAddr(), cache.DataTime, measuredValues, err)
		return
	}

	logger.Infof("ServerHandler", "sendMeasuredValueFloatByCP56Time2a send conn: %v t:%v var:%v", conn.UnderlyingConn().RemoteAddr(), cache.DataTime, measuredValues)
}

func (ms *ServerHandler) OnInterrogation(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierOfInterrogation) error {
	_ = SendRespond(conn, pack, asdu.ActivationCon)
	logger.Infof("ServerHandler", "OnInterrogation begin")

	newCache := db.LatestCache.GetF32Copy()

	sendMeasuredValueFloatByCP56Time2a(conn, newCache)

	go repeatRespond(conn, newCache)

	_ = SendRespond(conn, pack, asdu.ActivationTerm)
	return nil
}

func SendRespond(conn asdu.Connect, pack *asdu.ASDU, cause asdu.Cause) error {
	r := pack.Clone()
	r.Coa.Cause = cause
	err := r.AppendInfoObjAddr(0)
	if err != nil {
		return err
	}
	err = conn.Send(r)
	if err != nil {
		return err
	}
	return nil
}

func (ms *ServerHandler) OnCounterInterrogation(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierCountCall) error {
	//_ = pack.SendReplyMirror(conn, asdu.ActivationCon)
	//logger.Infof("ServerHandler", "OnCounterInterrogation")
	//// TODO
	//_ = asdu.CounterInterrogationCmd(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, asdu.QualifierCountCall{asdu.QCCGroup1, asdu.QCCFrzRead})
	//_ = pack.SendReplyMirror(conn, asdu.ActivationTerm)
	return nil
}

func (ms *ServerHandler) OnRead(conn asdu.Connect, pack *asdu.ASDU, addr asdu.InfoObjAddr) error {
	//_ = pack.SendReplyMirror(conn, asdu.ActivationCon)
	//logger.Infof("ServerHandler", "OnRead")
	//// TODO
	//_ = asdu.Single(conn, false, asdu.CauseOfTransmission{Cause: asdu.InterrogatedByStation}, commonAddr, asdu.SinglePointInfo{
	//	Ioa:   addr,
	//	Value: true,
	//	Qds:   asdu.QDSGood,
	//})
	//_ = pack.SendReplyMirror(conn, asdu.ActivationTerm)
	return nil
}

func (ms *ServerHandler) OnClockSync(conn asdu.Connect, pack *asdu.ASDU, tm time.Time) error {
	//_ = pack.SendReplyMirror(conn, asdu.ActivationCon)
	//logger.Infof("ServerHandler", "OnClockSync")
	//
	//now := time.Now()
	//_ = asdu.ClockSynchronizationCmd(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, now)
	//_ = pack.SendReplyMirror(conn, asdu.ActivationTerm)
	return nil
}

func (ms *ServerHandler) OnResetProcess(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierOfResetProcessCmd) error {
	//_ = pack.SendReplyMirror(conn, asdu.ActivationCon)
	//logger.Infof("ServerHandler", "OnResetProcess")
	//// TODO
	//_ = asdu.ResetProcessCmd(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, asdu.QPRGeneralRest)
	//_ = pack.SendReplyMirror(conn, asdu.ActivationTerm)
	return nil
}

func (ms *ServerHandler) OnDelayAcquisition(conn asdu.Connect, pack *asdu.ASDU, msec uint16) error {
	//_ = pack.SendReplyMirror(conn, asdu.ActivationCon)
	//logger.Infof("ServerHandler", "OnDelayAcquisition")
	//// TODO
	//_ = asdu.DelayAcquireCommand(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, msec)
	//_ = pack.SendReplyMirror(conn, asdu.ActivationTerm)
	return nil
}

func (ms *ServerHandler) OnASDU(conn asdu.Connect, pack *asdu.ASDU) error {
	//_ = pack.SendReplyMirror(conn, asdu.ActivationCon)
	//logger.Infof("ServerHandler", "OnASDU xxxxxxxxx")
	//// TODO
	//cmd := pack.GetSingleCmd()
	//_ = asdu.SingleCmd(conn, pack.Type, pack.Coa, pack.CommonAddr, asdu.SingleCommandInfo{
	//	Ioa:   cmd.Ioa,
	//	Value: cmd.Value,
	//	Qoc:   cmd.Qoc,
	//})
	//_ = pack.SendReplyMirror(conn, asdu.ActivationCon)
	return nil
}
